package es.uned.controlador;

import es.uned.modelo.Fantasma;
import es.uned.modelo.Pacman;
import es.uned.modelo.Blinky;
import es.uned.modelo.Clyde;
import es.uned.modelo.Mapa;
import es.uned.modelo.Pinky;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

/**
 *
 * @author Gabriel Manzano Mérida Esta classe gestiona el control de teclado,
 * tareas de pacman y los fantasmas, y los estados del juego.
 */
public class PacmanBoard extends javax.swing.JFrame {

    private Pacman pacman;
    private Timer pacmanTimer, ghostTimer;
    private PacmanPanel pacmanPanel;
    private Mapa mapa;
    private final LinkedList<Fantasma> fantasmas = new LinkedList<>();
    private int puntos = 0, ultimaTeclaPulsada = 0;
    private boolean enPausa = false, reiniciar = false, salir = false;
    private boolean gameOver = false, winner = false, intro = true;

    /**
     * Inicializa los flags y lanza initMisComponentes() para crear las instancias 
     * y lanzar el resto de métodos.
     */
    public PacmanBoard() {
        this.gameOver = false;
        this.reiniciar = false;
        this.winner = false;
        initMisComponentes();
    }

    /**
     * Crea las instancias principales del juego y lanza el resto de métodos.
     * El juego empieza con Pacman en movimiento
     *
     */
    private void initMisComponentes() {     

        //Creamos el panel y lo añadimos al frame
        this.pacmanPanel = new PacmanPanel();

        //creamos a pacman y lo añadimos al panel
        this.pacman = new Pacman(puntos, 10, 15);
        this.pacmanPanel.setPacman(pacman);
        add(pacmanPanel);

        //configuramos la ventana
        setSize(PacmanUtils.WIDTH * PacmanUtils.COLUMNAS + 5, 
                PacmanUtils.HEIGHT * PacmanUtils.FILAS + 16);
        setLocationRelativeTo(null);
        setTitle("Pacman");
        setVisible(true);

        //creamos los fantasmas y los cargamos en el panel
        this.cargarFantasmasAlMapa();

        //atendemos al teclado
        KeyListener listener = new MyKeyListener();
        addKeyListener(listener);
        setFocusable(true);

        //Getión de personajes
         //si queremos que empiece en movimiento KeyEvent.VK_LEFT
        // sino 0
        this.ultimaTeclaPulsada = 0;
        this.gestionaPacman();
        this.gestionaFantasmas();
        pacmanPanel.repaint();

        //lazamos sonido de intro
        PacmanSound.INTRO.play();
    }

    /**
     * Metodo privado para crear los personajes de los fantasmas. Al comenzar
     * tiene posiciones predeterminadas //Blinky, Pinky, Inky o Clyde
     */
    private void cargarFantasmasAlMapa() {
        Blinky fBlinky = new Blinky(9, 1);
        fantasmas.add(fBlinky);

        Pinky fPinky = new Pinky(15, 5);
        fantasmas.add(fPinky);

        Clyde fClyde = new Clyde(11, 9);
        fantasmas.add(fClyde);

        pacmanPanel.setFantasmas(fantasmas);
    }

    /**
     * Gestiona el timer y tareas de los fantasmas
     */
    private void gestionaFantasmas() {
        //ghostTimer
        this.ghostTimer = new Timer();
        TimerTask ghostTimerTask = new TimerTask() {
            @Override
            public void run() {
                //si no estamos en pausa 
                if (!enPausa && !intro) {
                    avanzarFantasmas();
                }
            }
        };
        //los fantasmas se desplazan mas lento, sino es imposible!
        pacmanTimer.scheduleAtFixedRate(ghostTimerTask, 1, 300);
    }

    /**
     * Gestiona el timer y tareas de pacman
     */
    private void gestionaPacman() {
        this.pacmanTimer = new Timer();
        TimerTask pacmanTimerTask = new TimerTask() {
            @Override
            public void run() {
                if (ultimaTeclaPulsada == KeyEvent.VK_ENTER) {
                    intro = false;
                    pacmanPanel.setIntro(false);
                    pacmanPanel.repaint();
                } else 
                //se ha pulsado la tecla p (pausa)
                if (ultimaTeclaPulsada == KeyEvent.VK_P) {
                    enPausa = !enPausa;
                    ultimaTeclaPulsada = 0;
                    //Si se acaba el juego y se pulsa R = reiniciar
                } else if (gameOver && ultimaTeclaPulsada == KeyEvent.VK_R) {
                    reiniciar = true;
                    reiniciarJuego();
                    //Si estamos en pausa o hemos acabado y se pulsa E = salir
                } else if ((enPausa && ultimaTeclaPulsada == KeyEvent.VK_E)
                        || (gameOver && ultimaTeclaPulsada == KeyEvent.VK_E)) {
                    salir = true;
                    System.exit(0);
                }   

                    //si se pulsa p para salir de la pausa, mandamos la señal al panel
                //y dejamos a pacman avanzar
                if (!enPausa && !intro) {
                    pacmanPanel.setPausa(false);
                    avanzarPacman();
                    //Si se pulsa p para pausa, mandamos la señal al panel y repintamos
                } else {
                    pacmanPanel.setPausa(true);
                    pacmanPanel.repaint();
                }

                //flags para gestionar el fin de la partida
                if (gameOver) {
                    pacman.setGameOver(true);
                    pacman.setWinner(winner);
                }

            }
        };
        pacmanTimer.scheduleAtFixedRate(pacmanTimerTask,1, 150);
    }

    /**
     * Hacemos avanzar a los fantasmas en un timer diferente para poder regular
     * la velocidad, sino pacman está en desventaja porque el teclado es lento
     */
    private void avanzarFantasmas() {

        //control de errores
        boolean fantasmasOk = fantasmas != null && !fantasmas.isEmpty();
        boolean pacmanOk = this.pacman != null;

        //si pacman sigue vivo
        if (pacmanOk && fantasmasOk && !this.gameOver) {
            Blinky blinky = (Blinky) this.fantasmas.get(0);
            Pinky pinky = (Pinky) this.fantasmas.get(1);
            Clyde clyde = (Clyde) this.fantasmas.get(2);

            //Si pacman es vulnerable los fantasmas van a por el 
            if (!this.pacman.isInvulnerable()) {
                blinky.avanza(this.mapa, this.pacman.getX(), pacman.getY());
                pinky.avanza(this.mapa, this.pacman.getX(), pacman.getY());
//                clyde.avanza(this.mapa, blinky.getX(), blinky.getY());
                clyde.avanza(this.mapa);

            } else { //if (this.pacman.isInvulnerable()){
                blinky.huye(this.mapa, this.pacman.getX(), this.pacman.getY());
                pinky.huye(this.mapa, this.pacman.getX(), this.pacman.getY());
                clyde.huye(this.mapa, this.pacman.getX(), this.pacman.getY());
            }
            //para evitar que los fantasmas se superpongan
            checkGhostCollision(blinky, pinky, clyde);

        }
    }

    /**
     * Compara las posiciones de los fantasmas 2 a 2. Si están en una misma
     * posición cambian de dirección.
     *
     * @param blinky
     * @param pinky
     * @param clyde
     */
    private void checkGhostCollision(Blinky blinky, Pinky pinky, Clyde clyde) {
        if (blinky.compareTo(pinky)) {
            blinky.setColision(true);
            pinky.setColision(true);
        }
        if (blinky.compareTo(clyde)) {
            blinky.setColision(true);
            clyde.setColision(true);
        }
        if (pinky.compareTo(clyde)) {
            pinky.setColision(true);
            clyde.setColision(true);
        }
    }

    /**
     * Actualizar estado del juego, puntos, posición de pacman, colisiones y
     * miedo de los fantasmas. Nota: El control de miedo de los fantasmas se
     * realiza aquí en vez de en el método avanzaFantasmas para evitar chequear
     * varias hacer varias veces: -- if (pacman.getTInvulnerable() ==
     * PacmanUtils.TMAX_INVULNERABLE) { -- además así nos aseguramos que no se
     * actualiza inútilmente el estado de miedo de los fantasmas.
     */
    private void avanzarPacman() {

        //control de errores
        boolean fantasmasOk = fantasmas != null && !fantasmas.isEmpty();
        boolean pacmanOk = this.pacman != null;

        //si todo ok
        if (pacmanOk && fantasmasOk) {
            //actualizamos el contador. Usamos un contador para pacman y otro externo
            //para saber si se ha comido un punto 
            this.puntos = pacman.getPuntos();

            //pacman avanza segun la tecla pulsada y actualiza sus parámetros      
            this.mapa = pacman.avanza(pacmanPanel.getMapa(), ultimaTeclaPulsada);

            //Los fantasmas se vuelven vulnerables cuando pacman come power pellets.
            //Pacman controla lo que se encuentra desde su clase. Si es una power pettet
            //se vuelve invulnerable durante 5 segundos (TMAX_INVULNERABLE), por lo  
            //que cada vez que avance debe reducir ese tiempo
            if (pacman.getTInvulnerable() == PacmanUtils.TMAX_INVULNERABLE) {

                //So pacman está cao es porque ha sido cafado por un fantasma.
                // de modo que los fantasmas huyen solo si pacman=ok.
                if (this.pacman.isOk()) {
                    this.fantasmas.get(0).setMiedo(true);
                    this.fantasmas.get(1).setMiedo(true);
                    this.fantasmas.get(2).setMiedo(true);
                }
                //sino es que ha muerto, o le han quitado una vida
                this.pacman.setTInvulnerable(this.pacman.getTInvulnerable() - 1);

                //La invulnerabilidad se acaba cuando el tInvulnerable=0, entonces vuelve a estar ok
            } else if (pacman.getTInvulnerable() == 0) {
                this.fantasmas.get(0).setMiedo(false);
                this.fantasmas.get(1).setMiedo(false);
                this.fantasmas.get(2).setMiedo(false);

                this.pacman.setInvulnerable(false);
                this.pacman.setOk(true);
            } else {
                this.pacman.setTInvulnerable(this.pacman.getTInvulnerable() - 1);
            }
        }

        //El juego acaba si pacmam vulnerable colisiona con fantasmas o 
        //completa el mapa
        if (detectarColisiones()) {
            //si pacman sigue vivo y no ha acabado el juego restamos una vida
            if (this.pacman.isOk() && !gameOver) {
                this.pacman.setVidas(this.pacman.getVidas() - 1);
                //cuando pierde una vida se vuelve invulnerable por poco tiempo
                this.pacman.setTInvulnerable(PacmanUtils.TMAX_INVULNERABLE);
                this.pacman.setOk(false);
            }
//            this.pacman.setOk(false);
            if (this.pacman.getVidas() == 0) {
                this.gameOver = true;
            }
        }

        //si el mapa está vacio hemos ganado => fin del juego (gameover)
        //y winner=true
        if (this.mapa.isEmpty()) {
            this.gameOver = true;
            this.winner = true;
        }

        //repintar el panel actualizado
        this.pacmanPanel.repaint();

        //Si (tecla a 0) pacman avanza solo cuando se pulsan los cursores
        //sino pacman continua en la dirección de la última tecla pulsada
//        autoPacmanOf(true); 
    }


    // Variables declaration - do not modify//GEN-BEGIN:variables
    // private javax.swing.JMenuBar jMenuBar;
    // End of variables declaration//GEN-END:variables
    /**
     * Comprueba si pacman colisiona con los fantasmas y si estos son
     * vulnerables
     *
     * @return
     */
    private boolean detectarColisiones() {
        boolean ok = false;
        //si todo va bien
        if (this.pacman != null && this.fantasmas != null && !this.fantasmas.isEmpty()) {

            //comprobamos las colisiones de pacman para cada fantasma
            for (int i = 0; i < this.fantasmas.size(); i++) {
                if (this.pacman.compareTo(this.fantasmas.get(i))) {
//                if (this.pacman.getX() == this.fantasmas.get(i).getX()
//                 && this.pacman.getY() == this.fantasmas.get(i).getY()) {
                    //si pacman es invulnerable
                    if (pacman.isInvulnerable()) {
                        pacman.setPuntos(pacman.getPuntos() + PacmanUtils.PUNTOS_FANTASMA);
                        this.fantasmas.get(i).aCasa();
                        PacmanSound.PACMAN_EAT_GHOST.play();
                        //sino se acaba el juego
                    } else {
                        //si pacman no ha sido tocado por un fantasma, no es invulnerable
                        //temporalmente
                        if (this.pacman.isOk()) {
                            ok = true;
                            PacmanSound.PACMAN_DEATH.play();
                        }
                    }
                }
            }
        }
        return ok;
    }

    /**
     * Esto hace que pacman se pare. Aunque es una linea, lo he metido en un
     * método que se explica con el nombre, ya que por si sola, la linea no dice
     * nada.
     *
     * @param b
     */
    private void autoPacmanOf(boolean b) {
        if (b) {
            ultimaTeclaPulsada = 0;
        }
    }

    /**
     * Añadimos un listener al panel para atender el teclado
     */
    private class MyKeyListener implements KeyListener {

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
            ultimaTeclaPulsada = e.getKeyCode();
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }
    }

    /**
     * Situa a los personajes en sus posiciones de inicio y resetea los estados
     * de panel y de pacman
     */
    private void reiniciarJuego() {
        if (this.pacman != null) {
            this.pacman.reiniciar();
        }

        if (this.fantasmas != null && !this.fantasmas.isEmpty()) {
            for (int i = 0; i < this.fantasmas.size(); i++) {
                this.fantasmas.get(i).reiniciar();
            }
        }

        this.mapa.reinciar();
        this.pacmanPanel.setMapa(this.mapa);
        this.pacmanPanel.setIntro(true);
        this.puntos = 0;
        this.ultimaTeclaPulsada = 0;
        this.enPausa = false;
        this.reiniciar = false;
        this.gameOver = false;
        this.winner = false;
        this.intro = true;
        
        PacmanSound.INTRO.play();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        new PacmanBoard();
    }
}
